1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
---
import FormField from '@/components/FormField.astro'
import { FORM_POST_URL } from '@/consts'
import Layout from '@/layouts/Layout.astro'
import { getAllForms } from '@/lib/data-utils'
import { formatDate } from '@/lib/utils'
import { Icon } from 'astro-icon/components'
import { render } from 'astro:content'
export async function getStaticPaths() {
const forms = await getAllForms()
return forms.map((form) => ({
params: { id: form.id },
props: form,
}))
}
const { Content } = await render(Astro.props)
const form = Astro.props.data
const fields = Astro.props.data.form.fields
---
<Layout>
<section
class="border-border mx-auto flex w-full max-w-3xl flex-col items-center gap-y-2 rounded-xl border p-8"
>
<h1
class="mb-2 w-full scroll-mt-31 text-center text-3xl leading-tight font-medium sm:text-4xl"
>
{form.title}
</h1>
<div
class="text-muted-foreground mb-6 flex w-fit py-2 text-xs sm:px-2 sm:py-0 sm:text-sm"
>
<span>{formatDate(form.date)}</span>
</div>
<article class="prose w-full">
<Content />
</article>
<form
action={`${FORM_POST_URL}${form.id}`}
data-form-id={form.id}
method="POST"
class="flex w-full flex-col items-start gap-12"
>
{fields.map((field) => <FormField field={field} />)}
<div
class="ml-auto grid w-full grid-cols-2 items-center justify-end gap-4 md:w-auto md:grid-cols-3"
>
<span
id="save-indicator"
class="text-muted-foreground ext-sm col-span-2 flex items-center justify-center gap-2 opacity-0 transition-opacity duration-300 md:col-span-1"
>
<Icon name="lucide:check" class="text-primary" />
Draft autosaved
</span>
<!-- Click again to confirm clear. -->
<button
type="button"
onclick="if(this.textContent==='Confirm?') { this.form.reset(); this.textContent='Clear'; this.classList.replace('bg-destructive', 'bg-muted'); } else { this.textContent='Confirm?'; this.classList.replace('bg-muted', 'bg-destructive'); this.classList.add('text-destructive-foreground'); }"
onblur="this.textContent='Clear'; this.classList.replace('bg-destructive', 'bg-muted'); this.classList.remove('text-destructive-foreground');"
class="bg-muted text-foreground rounded-lg px-4 py-2 font-sans text-lg font-bold transition-all duration-200"
>
Clear
</button>
<button
type="submit"
class="bg-primary text-primary-foreground rounded-lg px-4 py-2 font-sans text-lg font-bold"
>
{form.submit_text || 'Submit'}
</button>
</div>
</form>
</section>
<script>
const setupAutosave = () => {
const form = document.querySelector<HTMLFormElement>('form[data-form-id]')
const indicator = document.getElementById('save-indicator')
if (!form) return
const storageKey = `autosave-form-${form.dataset.formId}`
// load saved data on mount
const savedData = JSON.parse(localStorage.getItem(storageKey) || '{}')
Object.entries(savedData).forEach(([name, value]) => {
const inputs = form.querySelectorAll(`[name="${name}"]`)
inputs.forEach((input: any) => {
if (input.type === 'checkbox' || input.type === 'radio') {
input.checked = Array.isArray(value)
? value.includes(input.value)
: input.value === value
} else {
input.value = value
}
})
})
// save data to localStorage on any input change
form.addEventListener('input', () => {
const formData = new FormData(form)
const data: Record<string, any> = {}
for (const [key, value] of formData.entries()) {
if (data[key]) {
data[key] = [].concat(data[key], value as any)
} else {
data[key] = value
}
}
localStorage.setItem(storageKey, JSON.stringify(data))
// show the "Draft saved" indicator
if (indicator) {
indicator.classList.remove('opacity-0')
}
})
// clear localStorage on submit
form.addEventListener('submit', () => {
localStorage.removeItem(storageKey)
})
}
document.addEventListener('DOMContentLoaded', setupAutosave)
document.addEventListener('astro:page-load', setupAutosave)
</script>
</Layout>
|